import { Reducer } from 'react';
import { SET_CONDITION, SET_NOT, ADD_GROUP, ADD_RULE, REMOVE_RULE_GROUP, REMOVE_RULE, SET_FIELD, SET_OPERATOR, SET_VALUE, RESET_JSON_GROUP } from './action-types';
import { JsonGroup, Action, JsonPathGroup, JsonPathRuleGroup, JsonPathRule, ValueType, Config } from '../types';
import { getFieldInputByTypeAndOperator } from '../utils/fieldUtils';

const setCondition = (state: JsonGroup, type: "AND" | "OR", path: string): JsonGroup => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      item.condition = type;
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }

  _processNode(state);

  return { ...newState };
}

const setNot = (state: JsonGroup, flag: boolean, path: string): JsonGroup => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      item.not = flag;
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }

  _processNode(state);

  return { ...newState };
}

const addRuleGroup = (state: JsonGroup, group: JsonPathRuleGroup, path: string): JsonGroup => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      item.rules?.push(group);
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }

  _processNode(state);

  return { ...newState };
}

const addRule = (state: JsonGroup, rule: JsonPathRule, path: string) => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      item.rules?.push(rule);
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }
  _processNode(state);

  return { ...newState };
}

const removeRuleGroup = (state: JsonGroup, path: string) => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        if (rules[id].path === path) {
          item.rules = rules.filter(rule => rule.path != path);
          return;
        }
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }

  _processNode(state);

  return { ...newState };
}

const removeRule = (state: JsonGroup, path: string) => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        if (rules[id].path === path) {
          item.rules = rules.filter(rule => rule.path != path);
          return;
        }
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }
  _processNode(state);

  return { ...newState };
}

const setField = (state: JsonGroup, field: string, path: string) => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      const currentRule = (item as unknown) as JsonPathRule;
      currentRule.id = field;
      currentRule.field = field;
      currentRule.input = void 0;
      currentRule.operator = void 0;
      currentRule.type = void 0;
      currentRule.value = void 0;
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }
  _processNode(state);

  return { ...newState };
}

const setOperator = (state: JsonGroup, operator: string, config: Config, path: string) => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      const currentRule = (item as unknown) as JsonPathRule;
      const configFieldOperators = config.fields![currentRule.field as string].operators;
      const type = config.fields![currentRule.field as string].type;
      let inputType: string | undefined = void 0;
      if (configFieldOperators && configFieldOperators.length > 0) {
        const configFieldOperator = configFieldOperators.filter((val) => val.type == operator)[0];
        if (configFieldOperator) {
          inputType = configFieldOperator.input.type;
        } else {
          inputType = getFieldInputByTypeAndOperator(type, operator);
        }
      } else {
        inputType = getFieldInputByTypeAndOperator(type, operator);
      }
      currentRule.operator = operator;
      currentRule.type = type;
      currentRule.input = inputType;
      currentRule.value = void 0;
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }
  _processNode(state);

  return { ...newState };
}

const setValue = (state: JsonGroup, value: ValueType, path: string) => {
  let newState = state;

  function _processNode(item: JsonPathGroup | JsonPathRuleGroup) {
    if (item.path === path) {
      const currentRule = (item as unknown) as JsonPathRule;
      currentRule.value = value;
      return;
    }
    const rules = item.rules;
    if (rules) {
      for (let id in rules) {
        _processNode(rules[id] as JsonPathRuleGroup);
      }
    }
  }
  _processNode(state);

  return { ...newState };
}

export const reducer: Reducer<JsonGroup, Action> = (state, action) => {
  switch(action.type) {
    case SET_CONDITION:
      return setCondition(state, action.data.type, action.data.path);
    case SET_NOT:
      return setNot(state, action.data.flag, action.data.path);
    case ADD_GROUP:
      return addRuleGroup(state, action.data.group, action.data.path);
    case ADD_RULE:
      return addRule(state, action.data.rule, action.data.path);
    case REMOVE_RULE_GROUP:
      return removeRuleGroup(state, action.data.path);
    case REMOVE_RULE:
      return removeRule(state, action.data.path);
    case SET_FIELD:
      return setField(state, action.data.field, action.data.path);
    case SET_OPERATOR:
      return setOperator(state, action.data.operator, action.data.config, action.data.path);
    case SET_VALUE:
      return setValue(state, action.data.value, action.data.path);
    case RESET_JSON_GROUP:
      return {...action.data.jsonGroup};
    default:
      return state;
  }
}
